/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

// INCLUDES

#include "pymetadata_gc.hh"
#include "pymetadata.hh"
#include "pymetadata_global.hh"

// DEFINES
#define INC_TAB     4
#define MAX_TAB    32
#define INC_REF    32
#define INC_NODE  256

// PYMETADATA_GC CLASS

pymetadata_gc::pymetadata_gc()
{
    // create node list
    util::ctor_vector(node, n_node, max_node);
}

pymetadata_gc::~pymetadata_gc()
{
    // delete node list
    util::dtor_vector_delete(node, n_node, max_node);
}

bool pymetadata_gc::add(python_metadata *obj)
{
    if(!obj) ERROR_RX(false, "!obj");

    int i=util::inc_vector(node, n_node, max_node, INC_NODE);
    node[i]=new node_str(obj);

    if(!create_graph_recur(node[i], NULL)) ERROR_RX(false, "creating graph: %p", obj);
    return true;
}

bool pymetadata_gc::create_graph_recur(node_str *node, node_str *referer)
{
    // check
    if(!node->obj) ERROR_RX(false, "internal: !obj: referer=%p", referer?referer->obj:NULL);

    // add referer
    if(referer && !node->check_referer(referer)) node->add_referer(referer);

    // extract referencing objects
    PyObject *dict;
    if(!(dict=python_type_metadata::pygetstate_gc((PyObject *)node->obj)) || !PyDict_Check(dict)) // new ref
	ERROR_RX(false, "internal: getstate did not return a dict: node=%p referer=%p", node->obj, referer?referer->obj:NULL);

    // get list of items
    PyObject *items;
    if(!(items=PyDict_Values(dict)) || !PyList_Check(items)) // new ref
	ERROR_RX(false, "internal: dict.items(): node=%p referer=%p", node->obj, referer?referer->obj:NULL);
    Py_DECREF(dict);
    bool dev=create_graph_list_recur(items, node);
    Py_DECREF(items);
    if(!dev) ERROR_RX(false, "node=%p referer=%p", node->obj, referer?referer->obj:NULL);
    return true;
}

bool pymetadata_gc::create_graph_list_recur(PyObject *list, node_str *referer)
{
    int n=PyList_Size(list);
    for(int i=0; i<n; i++)
    {
	PyObject *item=PyList_GetItem(list, i); // borrowed
	if(PyList_Check(item))
	{
	    // list
	    if(!create_graph_list_recur(item, referer)) ERROR_RX(false, "list in list: referer=%p", referer?referer->obj:NULL);
	}
	else if(PyTuple_Check(item))
	{
	    // tuple
	    ERROR_RX(false, "TODO: tuple: referer=%p", referer?referer->obj:NULL);
	}
	else if(PyDict_Check(item))
	{
	    // dict
	    ERROR_RX(false, "TODO: dict");
	}
	else if(pymdg.check_type((python_type_metadata*)item->ob_type))
	{
	    // its a python_metadata
	    python_metadata *obj=(python_metadata*)item;

	    // check if in graph aready
	    node_str *n;
	    if((n=search_obj(obj)))
	    {
		// already exists, add referer
		if(referer && !n->check_referer(referer)) n->add_referer(referer);
	    }
	    else
	    {
		// create node
		int i=util::inc_vector(node, n_node, max_node, INC_NODE);
		node[i]=new node_str(obj);

		// make recursion
		if(!create_graph_recur(node[i], referer)) ERROR_RX(false, "pymetadata in list: referer=%p", referer?referer->obj:NULL);
	    }
	}
    }
    return true;
}

pymetadata_gc::node_str *pymetadata_gc::search_obj(python_metadata *obj)
{
    if(!obj) ERROR_RX(NULL, "searching none object in graph");

    for(int i=0; i<n_node; i++) if(node[i]->obj==obj) return node[i];
    return NULL;
}

int pymetadata_gc::search_ref(node_str *n, int from)
{
    if(!n) ERROR_RX(-1, "searching ref of none");

    for(int i=from; i<n_node; i++)
	if(node[i]->check_referer(n)) return i;
    return -1;
}

void pymetadata_gc::debug()
{
    for(int i=0; i<n_node; i++) node[i]->used=false;
    for(int i=0; i<n_node; i++) if(!node[i]->used) debug_recur(node[i], 0);
}

void pymetadata_gc::debug_recur(node_str *n, int tab)
{
    if(!n || !n->obj) ERROR_R("internal: bad node");

    // print node info
    if(n->used) printf(WHITE); else if(n->obj->get_obj_container()) printf(RED); else printf(CYAN);
    printf("%*s%p"RESET, tab, "", n->obj);
    if(n->used) printf("\n");
    else
    {
	printf("%*s: %2d %s%2d%s P%d "MAGENTA"%p "GREEN"%s"RESET"\n", MAX_TAB-tab, "",
	       ((PyObject*)n->obj)->ob_refcnt,
	       n->obj->has_refcnt()?WHITE:"",
	       n->obj->has_refcnt()?n->obj->get_refcnt():-1,
	       n->obj->has_refcnt()?RESET:"",
	       n->obj->get_obj_type()+1,
	       n->obj->get_obj_woind(),
	       n->obj->get_type_string());
    }

    // no more recursion if used
    if(n->used) return;
    n->used=true;

    // search where this node points
    int i_node=0;
    while((i_node=search_ref(n, i_node))>=0)
    {
	debug_recur(node[i_node], tab+INC_TAB);
	i_node++;
    }
}

// NODE_STR STRUCT

pymetadata_gc::node_str::node_str(python_metadata *obj_)
{
    obj=obj_;
    obj->python_incref();
    util::ctor_vector(ref, n_ref, max_ref);
    used=false;
}

pymetadata_gc::node_str::~node_str()
{
    if(obj) obj->python_decref();
    util::dtor_vector(ref, n_ref, max_ref);
}

bool pymetadata_gc::node_str::check_referer(node_str *referer)
{
    for(int i=0; i<n_ref; i++)
	if(ref[i]==referer) return true;
    return false;
}

void pymetadata_gc::node_str::add_referer(node_str *referer)
{
    int i=util::inc_vector(ref, n_ref, max_ref, INC_REF);
    ref[i]=referer;
}
